package com.cd2cd.code_template.util.vertify.service.impl;

import com.cd2cd.code_template.code_template.domain.SysUser;
import com.cd2cd.code_template.code_template.domain.gen.SysUserCriteria;
import com.cd2cd.code_template.code_template.mapper.SysUserMapper;
import com.cd2cd.code_template.configuration.constants.ServiceCode;
import com.cd2cd.code_template.configuration.exceptions.ServiceBusinessException;
import com.cd2cd.code_template.util.cache.DataCache;
import com.cd2cd.code_template.util.vertify.enums.VerificationCodeType;
import com.cd2cd.code_template.util.vertify.model.dto.UserValidateDto;
import com.cd2cd.code_template.util.vertify.model.vo.ImageVerificationVo;
import com.cd2cd.code_template.util.vertify.service.CaptchaService;
import com.cd2cd.code_template.util.vertify.utils.GoogleAuthenticator;
import com.cd2cd.code_template.util.vertify.utils.ImageRead;
import com.cd2cd.code_template.util.vertify.utils.ImageVerificationUtil;
import com.cd2cd.code_template.vo.BaseRes;
import com.google.code.kaptcha.Producer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;

import static com.cd2cd.code_template.util.vertify.utils.GoogleAuthenticator.generateSecretKey;
import static com.cd2cd.code_template.util.vertify.utils.GoogleAuthenticator.getQRBarcode;

/**
 * @Description: 验证码业务实现类
 * -------------------
 * @Author: YangXingfu
 * @Date: 2019/07/24 17:16
 */

@Slf4j
@Service
public class CaptchaServiceImpl implements CaptchaService {


    /**
     * 字符验证码
     */
    @Autowired
    private Producer captchaProducer;

    /**
     * 数值运算验证码
     */
    @Autowired
    private Producer captchaProducerMath;

    /**
     * 源图路径前缀
     */
    @Value("${captcha.slide-verification-code.path.origin-image:classpath:static/targets}")
    private String verificationImagePathPrefix;

    /**
     * 模板图路径前缀
     */
    @Value("${captcha.slide-verification-code.path.template-image:classpath:static/templates}")
    private String templateImagePathPrefix;


    @Autowired
    private SysUserMapper sysUserMapper;


    /**
     * 获取request对象
     *
     * @return 返回request对象
     */
    protected static HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 获取response对象
     *
     * @return 返回response对象
     */
    protected static HttpServletResponse getResponse() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    }


    /**
     * 根据类型获取验证码
     *
     * @param userValidateDto 用户信息
     * @return 图片验证码
     * @throws ServiceBusinessException 查询图片验证码异常
     */
    @Override
    public  BaseRes<ImageVerificationVo> selectImageVerificationCode(UserValidateDto userValidateDto) {

        ImageVerificationVo imageVerificationVo = null;
        String type = userValidateDto.getType();

        try {

            VerificationCodeType verificationCodeType = Enum.valueOf(VerificationCodeType.class, type.toUpperCase());

            switch (verificationCodeType) {
                //  获取运算验证码
                case OPERATION:
                    imageVerificationVo = selectOperationVerificationCode(userValidateDto);
                    break;
                //  获取字符验证码
                case CHAR:
                    imageVerificationVo = selectCharVerificationCode(userValidateDto);
                    break;
                //  获取滑动验证码
                case SLIDE:
                    imageVerificationVo = selectSlideVerificationCode(userValidateDto);
                    break;
                default:
                    break;
            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return new BaseRes<>(ServiceCode.FAILED);
        }
        return new BaseRes<>(imageVerificationVo,ServiceCode.SUCCESS);

    }


    /**
     * 获取运算验证码
     *
     * @param userValidateDto 用户信息
     * @return 运算验证吗
     * @throws ServiceBusinessException 查询运算验证码异常
     */
    private ImageVerificationVo selectOperationVerificationCode(UserValidateDto userValidateDto) throws ServiceBusinessException {

        byte[] bytes = null;
        String text = "";
        BufferedImage bufferedImage = null;
        ImageVerificationVo imageVerificationVo = null;

        try {

            imageVerificationVo = new ImageVerificationVo();
            imageVerificationVo.setType(userValidateDto.getType());
            //  生成运算验证码文本
            text = captchaProducerMath.createText();
            String value = text.substring(0, text.lastIndexOf("@"));
            String answer = text.substring(text.lastIndexOf("@")+1);

            //  生成运算验证码图片
            bufferedImage = captchaProducerMath.createImage(value);
            //  验证码存入redis
            //  getRequest().getSession().setAttribute("imageVerificationVo", imageVerificationVo);
            DataCache.setCacheData(userValidateDto.getUsername(),"OPERATION",answer);
            //  在分布式应用中，可将session改为redis存储
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
            bytes = byteArrayOutputStream.toByteArray();
            //  图片base64加密
            imageVerificationVo.setOperationImage(Base64Utils.encodeToString(bytes));
            imageVerificationVo.setType(userValidateDto.getType());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }
        return imageVerificationVo;

    }

    /**
     * 获取字符验证码
     *
     * @param userValidateDto 用户信息
     * @return 字符验证码
     * @throws ServiceBusinessException 获取字符验证码异常
     */
    private ImageVerificationVo selectCharVerificationCode(UserValidateDto userValidateDto) throws ServiceBusinessException {

        byte[] bytes = null;
        String text = "";
        BufferedImage bufferedImage = null;
        ImageVerificationVo imageVerificationVo = null;

        try {

            imageVerificationVo = new ImageVerificationVo();
            //  生成字符验证码文本
            text = captchaProducer.createText();
            //  生成字符验证码图片
            bufferedImage = captchaProducer.createImage(text);
//            getRequest().getSession().setAttribute("imageVerificationVo", imageVerificationVo);
            DataCache.setCacheData(userValidateDto.getUsername(),"CHAR",text);
            //  在分布式应用中，可将session改为redis存储
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
            bytes = byteArrayOutputStream.toByteArray();
            //  图片base64加密
            imageVerificationVo.setCharImage(Base64Utils.encodeToString(bytes));
            imageVerificationVo.setType("CHAR");

        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }
        return imageVerificationVo;

    }

    /**
     * 获取滑动验证码
     *
     * @param userValidateDto 验证码参数
     * @return 滑动验证码
     * @throws ServiceBusinessException 获取滑动验证码异常
     */
    ImageVerificationVo selectSlideVerificationCode(UserValidateDto userValidateDto) throws ServiceBusinessException {


        ImageVerificationVo imageVerificationVo = null;
        try {


            //  随机取得原图文件夹中一张图片
            ImageRead originImageRead = readTargetImage();
            //  获取模板图片文件
            ImageRead templateImageRead = readTemplateImage(templateImagePathPrefix.concat("/template.png"));
            //  获取描边图片文件
            ImageRead borderImageRead = readBorderImageFile(templateImagePathPrefix.concat("/border.png"));

            //  获取原图文件类型
            String originImageFileType = originImageRead.getFileExtension();
            //  获取模板图文件类型
            String templateImageFileType = templateImageRead.getFileExtension();
            //  获取边框图文件类型
            String borderImageFileType = borderImageRead.getFileExtension();

            //  读取原图
            BufferedImage verificationImage = originImageRead.getImage();
            //  读取模板图
            BufferedImage readTemplateImage = templateImageRead.getImage();

            //  读取描边图片
            BufferedImage borderImage = borderImageRead.getImage();


            //  获取原图感兴趣区域坐标
            imageVerificationVo = ImageVerificationUtil.generateCutoutCoordinates(verificationImage, readTemplateImage);

            int y = imageVerificationVo.getY();
            //  在分布式应用中，可将session改为redis存储
//            getRequest().getSession().setAttribute("imageVerificationVo", imageVerificationVo);
            DataCache.setCacheData(userValidateDto.getUsername(),"SLIDE",imageVerificationVo.getX());

            //  根据原图生成遮罩图和切块图
            imageVerificationVo = ImageVerificationUtil.pictureTemplateCutout(verificationImage, originImageRead.getInputStream(), originImageFileType, readTemplateImage, templateImageFileType, imageVerificationVo.getX(), imageVerificationVo.getY());

            //   剪切图描边
            imageVerificationVo = ImageVerificationUtil.cutoutImageEdge(imageVerificationVo, borderImage, borderImageFileType);
            imageVerificationVo.setY(y);
            imageVerificationVo.setType(userValidateDto.getType());


            //  =============================================
            //  输出图片
//            HttpServletResponse response = getResponse();
//            response.setContentType("image/jpeg");
//            ServletOutputStream outputStream = response.getOutputStream();
//            outputStream.write(oriCopyImages);
//            BufferedImage bufferedImage = ImageIO.read(originImageFile);
//            ImageIO.write(bufferedImage, originImageType, outputStream);
//            outputStream.flush();
            //  =================================================

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }

        return  imageVerificationVo;
    }

    /**
     * 读取目标图
     *
     * @return
     * @throws ServiceBusinessException
     */
    public ImageRead readTargetImage() throws ServiceBusinessException {
        ImageRead imageRead = null;

        try {
            Random random = new Random(System.currentTimeMillis());
            if (verificationImagePathPrefix.indexOf("classpath") >= 0) {
                ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                Resource[] resources = resolver.getResources(verificationImagePathPrefix.concat("/*"));

                if (resources == null) {
                    throw new RuntimeException("not found target image");
                }
                int i = random.nextInt(resources.length);
                imageRead = new ImageRead();
                imageRead.setImage(ImageIO.read(resources[i].getInputStream()));
                String extension = resources[i].getFilename().substring(resources[i].getFilename().lastIndexOf(".") + 1);
                imageRead.setInputStream(resources[i].getInputStream());
                imageRead.setFileExtension(extension);


            } else {
                File importImage = new File(verificationImagePathPrefix);
                if (importImage == null) {
                    throw new RuntimeException("not found target image");
                }
                File[] files = importImage.listFiles();
                int i = random.nextInt(files.length);
                imageRead = new ImageRead();
                imageRead.setImage(ImageIO.read(files[i]));
                String extension = files[i].getName().substring(files[i].getName().lastIndexOf(".") + 1);
                imageRead.setFileExtension(extension);
                imageRead.setInputStream(new FileInputStream(files[i]));
            }


        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }
        return imageRead;
    }

    /**
     * 读取模板图
     *
     * @param path
     * @return
     * @throws ServiceBusinessException
     */
    public ImageRead readTemplateImage(String path) throws ServiceBusinessException {
        ImageRead templateImageFile = null;
        try {
            if (templateImageFile != null) {
                return templateImageFile;
            }
            templateImageFile = new ImageRead();
            if (verificationImagePathPrefix.indexOf("classpath") >= 0) {
                ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                Resource resource = resolver.getResource(path);
                if (resource == null) {
                    throw new RuntimeException("not found template image");
                }
                templateImageFile.setImage(ImageIO.read(resource.getInputStream()));
                String extension = resource.getFilename().substring(resource.getFilename().lastIndexOf(".") + 1);
                templateImageFile.setInputStream(resource.getInputStream());
                templateImageFile.setFileExtension(extension);
            } else {
                File file = new File(path);
                templateImageFile.setImage(ImageIO.read(file));
                String extension = file.getName().substring(file.getName().lastIndexOf(".") + 1);
                templateImageFile.setInputStream(new FileInputStream(file));
                templateImageFile.setFileExtension(extension);

            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }
        return templateImageFile;
    }

    /**
     * 读取边框图
     *
     * @param path
     * @return
     * @throws ServiceBusinessException
     */
    public ImageRead readBorderImageFile(String path) {
        ImageRead borderImageFile = null;
        try {
            if (borderImageFile != null) {
                return borderImageFile;
            }
            borderImageFile = new ImageRead();
            if (templateImagePathPrefix.indexOf("classpath") >= 0) {
                ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
                Resource resource = resolver.getResource(path);
                if (resource == null) {
                    throw new RuntimeException("not found template image");
                }
                borderImageFile.setImage(ImageIO.read(resource.getInputStream()));
                String extension = resource.getFilename().substring(resource.getFilename().lastIndexOf(".") + 1);
                borderImageFile.setInputStream(resource.getInputStream());
                borderImageFile.setFileExtension(extension);
            } else {
                File file = new File(path);
                borderImageFile.setImage(ImageIO.read(file));
                String extension = file.getName().substring(file.getName().lastIndexOf(".") + 1);
                borderImageFile.setInputStream(new FileInputStream(file));
                borderImageFile.setFileExtension(extension);
            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceBusinessException(ServiceCode.FAILED,e.getMessage());
        }
        return borderImageFile;
    }


    @Override
    @Transactional
    public BaseRes<String> genUserSecretKey(UserValidateDto userValidateDto) {

        UserValidateDto.ApplyType applyType = Enum.valueOf(UserValidateDto.ApplyType.class, userValidateDto.getApplyType().toUpperCase());
        switch (applyType){
            case SYSUSER:
                return sysUserGenSecretKey(userValidateDto);
            case PLATFUSER:break;
            default:break;
        }

        return new BaseRes<>(ServiceCode.FAILED);
    }


    private BaseRes<String>  sysUserGenSecretKey(UserValidateDto userValidateDto){
        SysUserCriteria userCriteria = new SysUserCriteria();
        userCriteria.createCriteria().andDelFlagEqualTo("ok").andUsernameEqualTo(userValidateDto.getUsername());
        List<SysUser> sysUsers = sysUserMapper.selectByExample(userCriteria);
        if(!CollectionUtils.isEmpty(sysUsers)){
            SysUser sysUser = sysUsers.get(0);
            boolean present = Optional.ofNullable(sysUser.getSecretKey()).isPresent();
            if(!present){
                String  secretKey = generateSecretKey();
                sysUser.setSecretKey(secretKey);
                sysUserMapper.updateByPrimaryKey(sysUser);
                return new BaseRes<>(getQRBarcode(userValidateDto.getUsername(), secretKey),ServiceCode.SUCCESS);
            }else{
                return new BaseRes<>(ServiceCode.REPEAT_APPLY_USERKEY);
            }
        }else{
            return new BaseRes<>(ServiceCode.UNKNOWN_USER);
        }

    }

    @Override
    public BaseRes<String> checkVerificationResult(UserValidateDto userValidateDto) {

        SysUserCriteria userCriteria = new SysUserCriteria();
        GoogleAuthenticator ga = new GoogleAuthenticator();

        String username = userValidateDto.getUsername();
        String type = userValidateDto.getType();
        String answer = userValidateDto.getAnswer();

        userCriteria.createCriteria().andDelFlagEqualTo("ok").andUsernameEqualTo(username);
        List<SysUser> sysUsers = sysUserMapper.selectByExample(userCriteria);
        if(CollectionUtils.isEmpty(sysUsers)){
            return new BaseRes<>(ServiceCode.UNKNOWN_USER);
        }

        VerificationCodeType verificationCodeType = Enum.valueOf(VerificationCodeType.class, type.toUpperCase());
        switch (verificationCodeType) {
            //  获取运算验证码
            case OPERATION:
                //  获取字符验证码
            case CHAR:
                String  strCode =   Optional.ofNullable((String)DataCache.getCacheData(username,type.toUpperCase())).filter(Objects::nonNull).orElse(null);
                if(!StringUtils.isEmpty(strCode) && strCode.equals(answer)){
                    return new BaseRes<>(ServiceCode.SUCCESS);
                }
                break;
            //  获取滑动验证码
            case SLIDE:
                Integer  intCode = Optional.ofNullable((Integer)DataCache.getCacheData(username,type.toUpperCase())).filter(Objects::nonNull).orElse(0);
                if ((Math.abs(Integer.parseInt(answer) - intCode) <= 5)) {
                    return new BaseRes<>(ServiceCode.SUCCESS);
                }
                break;
            case TOTP:
                String secretKey = Optional.ofNullable(sysUsers.get(0).getSecretKey()).filter(Objects::nonNull).orElse(null);
                ga.setWindowSize(3);
                Long t =System.currentTimeMillis();
                if(ga.check_code(secretKey, Long.valueOf(answer), t)){
                    return new BaseRes<>(ServiceCode.SUCCESS);
                }
                break;
            default:
                break;
        }
        return new BaseRes<>(ServiceCode.FAILED);

    }




}